#include "qemu/osdep.h"
#include "hw/irq.h"
#include "hw/sysbus.h"
#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"

#ifndef DEBUG_HS0606_GPIO
#define DEBUG_HS0606_GPIO 0
#endif

#define TYPE_HS0606_GPIO "hs0606"
#define HS0606_GPIO(obj) OBJECT_CHECK(hs0606_state, (obj), TYPE_HS0606_GPIO)

#define HS0606_GPIO_MAX_PIN_COUNT 32

#define HS0606_GPIO_SWPORT_DR           (0x00 / 4)
#define HS0606_GPIO_SWPORT_DDR          (0x04 / 4)
#define HS0606_GPIO_PORT_CTRL           (0x08 / 4)
#define HS0606_GPIO_INT_EN              (0x30 / 4)
#define HS0606_GPIO_INMASK              (0x34 / 4)
#define HS0606_GPIO_INTTYPE_LEVEL       (0x38 / 4)
#define HS0606_GPIO_INT_POLARITY        (0x3c / 4)
#define HS0606_GPIO_INTSTATUS           (0x40 / 4)
#define HS0606_GPIO_RAW_INTSTATUS       (0x44 / 4)
#define HS0606_GPIO_DEBOUNCE            (0x48 / 4)
#define HS0606_GPIO_PORT_EOI            (0x4c / 4)
#define HS0606_GPIO_EXT_PORT            (0x50 / 4)
#define HS0606_GPIO_LS_SYNC             (0x60 / 4)

#define REG_NUM     (0xF0 / 4)

typedef struct hs0606_state {
    /* < private > */
    SysBusDevice parent_obj;

    /* < public > */
    MemoryRegion iomem;

    uint32_t in_val;
    uint32_t out_val;
    uint32_t regs[REG_NUM];
    qemu_irq irq;
    qemu_irq output[HS0606_GPIO_MAX_PIN_COUNT];
} hs0606_state;

static const VMStateDescription vmstate_hs0606 = {
    .name = TYPE_HS0606_GPIO,
    .version_id = 1,
    .minimum_version_id = 1,
    .minimum_version_id_old = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(in_val, hs0606_state),
        VMSTATE_UINT32(out_val, hs0606_state),
        VMSTATE_UINT32_ARRAY(regs, hs0606_state, REG_NUM),
        VMSTATE_END_OF_LIST()
    }
};

static void hs0606_update_interrupt(hs0606_state *s, uint32_t input_changed)
{
    for (uint32_t bm = 1, i = 0; i < HS0606_GPIO_MAX_PIN_COUNT; bm <<= 1, i++) {
        if ((s->regs[HS0606_GPIO_SWPORT_DDR] & bm) == 0) {
            /* pin is output */
            s->regs[HS0606_GPIO_RAW_INTSTATUS] &= ~bm;
            continue;
        }
        if ((s->regs[HS0606_GPIO_INT_EN] & bm) == 0) {
            /* interrupt disabled */
            s->regs[HS0606_GPIO_RAW_INTSTATUS] &= ~bm;
            continue;
        }
        if ((s->regs[HS0606_GPIO_INTTYPE_LEVEL] & bm) == 0) {
            /* level interrupt */
            uint32_t int_polarity = s->regs[HS0606_GPIO_INT_POLARITY] & bm;
            const uint32_t level = (int_polarity ? s->in_val : ~s->in_val) & bm;
            s->regs[HS0606_GPIO_RAW_INTSTATUS] = s->regs[HS0606_GPIO_RAW_INTSTATUS] & ~bm & level;
            continue;
        }
        if ((input_changed & bm) == 0) {
            continue;
        }
        if (s->regs[HS0606_GPIO_INTTYPE_LEVEL] & bm) {
            /* edge interrupt */
            uint32_t int_polarity = s->regs[HS0606_GPIO_INT_POLARITY] & bm;
            const uint32_t level = (int_polarity ? s->in_val : ~s->in_val) & bm;
            s->regs[HS0606_GPIO_RAW_INTSTATUS] = s->regs[HS0606_GPIO_RAW_INTSTATUS] & ~bm & level;
        }
    }
    s->regs[HS0606_GPIO_INTSTATUS] = s->regs[HS0606_GPIO_RAW_INTSTATUS] & ~s->regs[HS0606_GPIO_INMASK];
    qemu_set_irq(s->irq, s->regs[HS0606_GPIO_INTSTATUS] ? 1 : 0);
}

static void hs0606_clear_interrupt(hs0606_state *s, uint32_t clear_mask)
{
    s->regs[HS0606_GPIO_RAW_INTSTATUS] &= ~(s->regs[HS0606_GPIO_INTTYPE_LEVEL] & clear_mask);
    s->regs[HS0606_GPIO_INTSTATUS] = s->regs[HS0606_GPIO_RAW_INTSTATUS] & ~s->regs[HS0606_GPIO_INMASK];
    qemu_set_irq(s->irq, s->regs[HS0606_GPIO_INTSTATUS] ? 1 : 0);
}

static void hs0606_gpio_set(void *opaque, int line, int level)
{
    hs0606_state *s = HS0606_GPIO(opaque);
    const uint32_t mask = (uint32_t)1 << line;
    const uint32_t old_val = s->in_val;
    if (level) {
        s->in_val |= mask;
    } else {
        s->in_val &= ~mask;
    }
    if ((s->regs[HS0606_GPIO_SWPORT_DDR] & mask) != 0 && old_val != s->in_val) {
        hs0606_update_interrupt(s, mask);
    }
}

static uint64_t hs0606_read(void *opaque, hwaddr offset, unsigned size)
{
    hs0606_state *s = (hs0606_state *)opaque;
    uint32_t input_value;
    uint32_t output_value;
    offset >>=0x2;

    switch (offset) {
        case HS0606_GPIO_SWPORT_DR:
        case HS0606_GPIO_SWPORT_DDR:
        case HS0606_GPIO_PORT_CTRL:
        case HS0606_GPIO_INT_EN:
        case HS0606_GPIO_INMASK:
        case HS0606_GPIO_INTTYPE_LEVEL:
        case HS0606_GPIO_INT_POLARITY:
        case HS0606_GPIO_INTSTATUS:
        case HS0606_GPIO_RAW_INTSTATUS:
        case HS0606_GPIO_LS_SYNC:
        case HS0606_GPIO_DEBOUNCE:
            return s->regs[offset];
        case HS0606_GPIO_EXT_PORT:
            output_value = s->regs[HS0606_GPIO_SWPORT_DR] & s->regs[HS0606_GPIO_SWPORT_DDR];
            input_value = s->in_val & ~s->regs[HS0606_GPIO_SWPORT_DDR];
            return output_value | input_value;
            
        default:
            output_value = s->regs[HS0606_GPIO_SWPORT_DR] & s->regs[HS0606_GPIO_SWPORT_DDR];
            input_value = s->in_val & ~s->regs[HS0606_GPIO_SWPORT_DDR];
            return output_value | input_value;
    }
    return 0;
}

static void hs0606_update_output(hs0606_state *s, uint32_t output_new)
{
    s->regs[HS0606_GPIO_SWPORT_DR] = output_new;
    s->out_val = output_new & s->regs[HS0606_GPIO_SWPORT_DDR];
    for (uint32_t i = 0, bm = 1; i < HS0606_GPIO_MAX_PIN_COUNT; bm <<= 1, i++) {
        qemu_set_irq(s->output[i], (s->out_val & bm) == 0 ? 0 : 1);
    }
}

static void hs0606_write(void *opaque, hwaddr offset, uint64_t value, unsigned size)
{
    hs0606_state *s = (hs0606_state *)opaque;
    offset >>= 0x2;
    
    switch (offset) {
        case HS0606_GPIO_SWPORT_DR:
            hs0606_update_output(s, (uint32_t)value);
            break;
        case HS0606_GPIO_SWPORT_DDR:
            s->regs[offset] = value;
            hs0606_update_output(s, s->regs[HS0606_GPIO_SWPORT_DR]);
            hs0606_update_interrupt(s, 0);
            break;
        case HS0606_GPIO_DEBOUNCE:
        case HS0606_GPIO_LS_SYNC:
            s->regs[offset] = value;
            break;
        case HS0606_GPIO_INT_EN:
        case HS0606_GPIO_INMASK:
        case HS0606_GPIO_INTTYPE_LEVEL:
        case HS0606_GPIO_INT_POLARITY:
            s->regs[offset] = value;
            hs0606_update_interrupt(s, 0);
            break;
        case HS0606_GPIO_PORT_EOI:
            hs0606_clear_interrupt(s, (uint32_t)value);
            break;
        default:
            break;
    }
}

static const MemoryRegionOps hs0606_ops = {
    .read = hs0606_read,
    .write = hs0606_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .valid.min_access_size = 4,
    .valid.max_access_size = 4,
};

static Property hs0606_properties[] = {
    DEFINE_PROP_END_OF_LIST(),
};

static void hs0606_reset(DeviceState *dev)
{
    hs0606_state *s = HS0606_GPIO(dev);
    for (uint32_t i = 0; i < REG_NUM; i++) {
        s->regs[i] = 0;
    }
}

static void hs0606_realize(DeviceState *dev, Error **errp)
{
    hs0606_state *s = HS0606_GPIO(dev);

    memory_region_init_io(&s->iomem, OBJECT(s), &hs0606_ops, s,
                          TYPE_HS0606_GPIO, 0x1000);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
    qdev_init_gpio_in(DEVICE(s), hs0606_gpio_set, HS0606_GPIO_MAX_PIN_COUNT);
    qdev_init_gpio_out(DEVICE(s), s->output, HS0606_GPIO_MAX_PIN_COUNT);
}

static void hs0606_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = hs0606_realize;
    dc->reset = hs0606_reset;
    device_class_set_props(dc, hs0606_properties);
    dc->vmsd = &vmstate_hs0606;
    dc->desc = "HS0606 GPIO controller";
}

static const TypeInfo hs0606_info = {
    .name = TYPE_HS0606_GPIO,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(hs0606_state),
    .class_init = hs0606_class_init,
};

static void hs0606_register_types(void)
{
    type_register_static(&hs0606_info);
}

type_init(hs0606_register_types)
